1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 module hip.image; 12 public import hip.api.data.image; 13 14 15 IHipBMPDecoder bmp; 16 IHipJPEGDecoder jpeg; 17 IHipPNGDecoder png; 18 IHipWebPDecoder webP; 19 20 21 version(HipARSDImageDecoder) 22 final class HipARSDImageDecoder : IHipAnyImageDecoder 23 { 24 import arsd.image; 25 MemoryImage img; 26 TrueColorImage trueImg; 27 string path; 28 this(string path = "") 29 { 30 this.path = path; 31 } 32 bool startDecoding(ubyte[] data, void delegate() onSuccess, void delegate() onFailure) 33 { 34 img = loadImageFromMemory(data); 35 if(img !is null) 36 { 37 trueImg = img.getAsTrueColorImage; 38 onSuccess(); 39 } 40 else 41 onFailure(); 42 43 return (img !is null) && (trueImg !is null); 44 } 45 46 uint getWidth() const 47 { 48 if(img !is null) 49 return img.width; 50 return 0; 51 } 52 53 uint getHeight() const 54 { 55 if(img !is null) 56 return img.height; 57 return 0; 58 } 59 60 const(ubyte[]) getPixels() const 61 { 62 if(img !is null) 63 return trueImg.imageData.bytes; 64 return null; 65 } 66 67 ubyte getBytesPerPixel() const 68 { 69 //Every true image color has 4 bytes per pixel 70 return 4; 71 } 72 73 ubyte[] getPalette() const 74 { 75 return null; 76 } 77 78 void dispose() 79 { 80 img.clearInternal; 81 destroy(trueImg); 82 destroy(img); 83 } 84 } 85 86 87 version(HipGamutImageDecoder) 88 final class HipGamutImageDecoder : IHipAnyImageDecoder 89 { 90 import gamut; 91 Image img; 92 string path; 93 this(string path = "") 94 { 95 this.path = path; 96 } 97 bool startDecoding(ubyte[] data, void delegate() onSuccess, void delegate() onFailure) 98 { 99 img.loadFromMemory(data, LOAD_RGB | LOAD_ALPHA | LOAD_8BIT); 100 if(img.isValid) 101 { 102 img.changeLayout(LAYOUT_GAPLESS | LAYOUT_VERT_STRAIGHT); 103 onSuccess(); 104 } 105 else 106 onFailure(); 107 return img.isValid; 108 } 109 110 uint getWidth() const 111 { 112 if(img.isValid) 113 return img.width; 114 return 0; 115 } 116 117 uint getHeight() const 118 { 119 if(img.isValid) 120 return img.height; 121 return 0; 122 } 123 124 const(ubyte[]) getPixels() const 125 { 126 if(img.isValid) 127 return img.allPixelsAtOnce(); 128 return null; 129 } 130 131 ubyte getBytesPerPixel() const 132 { 133 final switch(img.type) with(PixelType) 134 { 135 case l8: return 1; 136 case l16: return 2; 137 case lf32: return 4; 138 case la8: return 2; 139 case la16: return 4; 140 case laf32: return 8; 141 case rgb8: return 3; 142 case rgb16: return 6; 143 case rgbf32: return 12; 144 case rgba8: return 4; 145 case rgba16: return 8; 146 case rgbaf32: return 16; 147 case unknown: assert(false, "Invalid image?"); 148 } 149 } 150 ///Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. 151 ubyte[] getPalette() const{return null;} 152 153 void dispose() 154 { 155 destroy(img); 156 } 157 } 158 159 final class HipNullImageDecoder : IHipAnyImageDecoder 160 { 161 this(string path){} 162 bool startDecoding(void[] data, void delegate() onSuccess, void delegate() onFailure) 163 { 164 onFailure(); 165 return false; 166 } 167 uint getWidth() const {return 0;} 168 uint getHeight() const {return 0;} 169 const(ubyte)[] getPixels() const {return null;} 170 ubyte getBytesPerPixel() const {return 0;} 171 const(ubyte)[] getPalette() const {return null;} 172 void dispose(){} 173 } 174 175 176 version(WebAssembly) 177 { 178 import hip.wasm; 179 extern(C) struct BrowserImage 180 { 181 size_t handle; 182 bool valid() const {return handle > 0;} 183 alias handle this; 184 } 185 //Returns a BrowserImage, but can't use it in type directly. 186 extern(C) size_t WasmDecodeImage( 187 size_t imgPathLength, char* imgPathChars, ubyte* data, size_t dataSize, 188 JSDelegateType!(void delegate(BrowserImage)) onImageLoad 189 ); 190 191 extern(C) size_t WasmImageGetWidth(size_t); 192 extern(C) size_t WasmImageGetHeight(size_t); 193 extern(C) ubyte* WasmImageGetPixels(size_t); 194 extern(C) void WasmImageDispose(size_t); 195 196 final class HipWasmImageDecoder : IHipAnyImageDecoder 197 { 198 //Everything here needs to be cached for not calling the Wasm bridge. 199 private uint width, height; 200 private size_t timePixelsGet = 0; 201 BrowserImage img; 202 string path; 203 ubyte[] pixels; 204 this(string path) 205 { 206 assert(path, "HipWasmImageDecoder requires a path."); 207 this.path = path; 208 } 209 bool startDecoding(void[] data, void delegate() onSuccess, void delegate() onFailure) 210 { 211 import hip.console.log; 212 img = WasmDecodeImage(path.length, cast(char*)path.ptr, cast(ubyte*)data.ptr, data.length, sendJSDelegate!((BrowserImage _img) 213 { 214 assert(img == _img, "Different image returned!"); 215 if(img.valid) 216 { 217 width = WasmImageGetWidth(img); 218 height = WasmImageGetHeight(img); 219 pixels = getWasmBinary(WasmImageGetPixels(img)); 220 hiplog(width, " x ", height, " ", pixels.length, " bytes"); 221 222 (width != 0 && height != 0) ? onSuccess() : onFailure(); 223 } 224 else 225 { 226 loglnError("Corrupted JS image object."); 227 onFailure(); 228 } 229 }).tupleof); 230 231 return img.valid && width != 0 && height != 0; 232 } 233 uint getWidth() const {return width;} 234 uint getHeight() const {return height;} 235 const(ubyte)[] getPixels() const 236 { 237 return cast(const(ubyte)[])pixels; 238 } 239 ubyte getBytesPerPixel() const {return 4;} 240 const(ubyte)[] getPalette() const {return null;} 241 void dispose() 242 { 243 assert(img.valid, "Invalid dispose call."); 244 WasmImageDispose(img); 245 freeWasmBinary(pixels); 246 img = 0; 247 pixels = null; 248 } 249 } 250 } 251 252 253 public class HipImageImpl : IImage 254 { 255 IHipImageDecoder decoder; 256 string imagePath; 257 int width, height; 258 ubyte bytesPerPixel; 259 ushort bitsPerPixel; 260 261 ubyte[] pixels; 262 this(string path = "") 263 { 264 imagePath = path; 265 decoder = new HipPlatformImageDecoder(path); 266 } 267 268 static immutable(IImage) getPixelImage() 269 { 270 __gshared HipImageImpl img; 271 __gshared ubyte[4] pixel = IHipImageDecoder.getPixel(); 272 if(img is null) 273 { 274 img = new HipImageImpl("Pixel"); 275 img.pixels = pixel; 276 img.width = 1; 277 img.height = 1; 278 img.bytesPerPixel = 4; 279 } 280 return cast(immutable)img; 281 } 282 string getName() const {return imagePath;} 283 uint getWidth() const {return width;} 284 uint getHeight() const {return height;} 285 ubyte getBytesPerPixel() const {return bytesPerPixel;} 286 const(ubyte[]) getPalette() const {return decoder.getPalette;} 287 const(ubyte[]) getPixels() const {return pixels;} 288 289 void loadRaw(in ubyte[] pixels, int width, int height, ubyte bytesPerPixel) 290 { 291 this.width = width; 292 this.height = height; 293 this.pixels = cast(ubyte[])pixels; 294 this.bytesPerPixel = bytesPerPixel; 295 this.bitsPerPixel = cast(ubyte)(bytesPerPixel*8); 296 } 297 298 299 bool loadFromMemory(ubyte[] data, void delegate(IImage self) onSuccess, void delegate() onFailure) 300 { 301 import hip.error.handler; 302 if(ErrorHandler.assertErrorMessage(data.length != 0, "No data was passed to load Image.", "Could not load image")) 303 return false; 304 if(ErrorHandler.assertLazyErrorMessage(decoder.startDecoding(data, () 305 { 306 width = decoder.getWidth(); 307 height = decoder.getHeight(); 308 bitsPerPixel = decoder.getBitsPerPixel(); 309 bytesPerPixel = decoder.getBytesPerPixel(); 310 pixels = cast(ubyte[])decoder.getPixels(); 311 onSuccess(this); 312 }, onFailure), 313 "Decoding Image: ", "Could not load image " ~ imagePath)) 314 return false; 315 316 return true; 317 } 318 319 bool hasLoadedData() const {return pixels !is null && width != 0 && height != 0;} 320 321 ubyte[] monochromeToRGBA() const 322 { 323 import hip.error.handler; 324 ubyte[] pix = new ubyte[](4*width*height); //RGBA for each pixel 325 ErrorHandler.assertExit(pix != null, "Out of memory when converting monochrome to RGBA"); 326 uint pixelsLength = width*height; 327 ubyte color; 328 uint z; 329 for(uint i = 0; i < pixelsLength; i++) 330 { 331 //Palette r color = palette[pixels[i]*4] 332 color = (cast(ubyte*)pixels)[i]; 333 pix[z++] = color; //R 334 pix[z++] = color; //G 335 pix[z++] = color; //B 336 pix[z++] = color; //A 337 } 338 339 return pix; 340 } 341 342 ubyte[] convertPalettizedToRGBA() const 343 { 344 import hip.error.handler; 345 ubyte[] pix = new ubyte[](4*width*height); //RGBA for each pixel 346 ErrorHandler.assertExit(pix != null, "Out of memory when converting palette pixels to RGBA"); 347 348 uint pixelsLength = width*height; 349 const(ubyte[]) palette = decoder.getPalette(); 350 351 uint colorIndex; 352 uint z; 353 for(uint i = 0; i < pixelsLength; i++) 354 { 355 //Palette r color = palette[pixels[i]*4] 356 colorIndex = (cast(ubyte*)pixels)[i]*4; 357 pix[z++] = palette[colorIndex]; //R 358 pix[z++] = palette[colorIndex+1]; //G 359 pix[z++] = palette[colorIndex+2]; //B 360 pix[z++] = palette[colorIndex+3]; //A 361 } 362 363 return pix; 364 } 365 366 void dispose() 367 { 368 decoder.dispose(); 369 } 370 alias w = width; 371 alias h = height; 372 } 373 374 375 ///Use that alias for supporting more platforms 376 version(HipARSDImageDecoder) alias HipPlatformImageDecoder = HipARSDImageDecoder; 377 else version(HipGamutImageDecoder) alias HipPlatformImageDecoder = HipGamutImageDecoder; 378 else version(WebAssembly) alias HipPlatformImageDecoder = HipWasmImageDecoder; 379 else 380 { 381 alias HipPlatformImageDecoder = HipNullImageDecoder; 382 pragma(msg, "WARNING: Using NullImageDecoder."); 383 }